// Copyright (C) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Runtime.InteropServices;

namespace Microsoft.VSPowerToys.VSCmdShell
{
    /// <summary>
    /// Options dialog window for VSCmdShell add-in.
    /// 
    /// User can choose between different shells and font settings in this dialog.
    /// </summary>
    /// <seealso cref="EnvDTE.IDTToolsOptionsPage"/>
    public partial class OptionsDialog : UserControl, EnvDTE.IDTToolsOptionsPage
    {

        #region Private Variables

        /// <summary>
        /// Names of the users current shell (to be compared to later when user clicks OK)
        /// </summary>
        private string previouslySelectedShell;

        #endregion

        public OptionsDialog()
        {
            InitializeComponent();
        }

        #region Event Handlers

        /// <summary>
        /// Loads the shell entries and also adjusts the properties of controls on the dialog from
        /// settings file.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OptionsDialog_Load(object sender, EventArgs e)
        {
            this.uxFontDialog.AllowVerticalFonts = false;
            this.uxFontDialog.FixedPitchOnly = true;
            this.uxFontDialog.Font = Properties.Settings.Default.ConsoleFont;
            this.uxColorDialog.Color = Properties.Settings.Default.ConsoleFontColor;
            this.uxBackColorDialog.Color = Properties.Settings.Default.ConsoleFontBackColor;
            this.uxSample.Font = Properties.Settings.Default.ConsoleFont;
            this.uxSample.ForeColor = Properties.Settings.Default.ConsoleFontColor;
            this.uxSample.BackColor = Properties.Settings.Default.ConsoleFontBackColor;
            this.uxFontUseVS.Checked = Properties.Settings.Default.ConsoleFontUseVS;
            this.previouslySelectedShell = Properties.Settings.Default.SelectedShellTypeName;
            new ToolTip().SetToolTip(this.uxFontUseVS, Strings.Options_UseFontVSTooltip);
            this.FillStartingDirectoryPreferences();
            LoadShellEntries();
        }

        private void ShowFontChangeDialog(object sender, EventArgs e)
        {
            DialogResult dr = this.uxFontDialog.ShowDialog();
            if (dr == DialogResult.OK)
            {
                this.uxSample.Font = this.uxFontDialog.Font;
            }
        }

        private void ShowFontColorChangeDialog(object sender, EventArgs e)
        {
            DialogResult dr = this.uxColorDialog.ShowDialog();
            if (dr == DialogResult.OK)
            {
                this.uxSample.ForeColor = this.uxColorDialog.Color;
            }
        }

        /// <summary>
        /// Enable/Disable form controls based on the new choice
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void uxFontUseVS_CheckedChanged(object sender, EventArgs e)
        {
            this.uxFontChange.Enabled = !this.uxFontUseVS.Checked;
            this.uxColorChange.Enabled = !this.uxFontUseVS.Checked;
            this.uxBackColorChange.Enabled = !this.uxFontUseVS.Checked;
            if (this.uxFontUseVS.Checked)
            {
                this.uxSample.Font = Utils.CodeWindowFont;
                this.uxSample.ForeColor = Utils.CodeWindowForeColor;
                this.uxSample.BackColor = Utils.CodeWindowBackColor;
            }
            else
            {
                this.uxSample.Font = this.uxFontDialog.Font;
                this.uxSample.ForeColor = this.uxColorDialog.Color;
                this.uxSample.BackColor = this.uxBackColorDialog.Color;
            }
        }

        /// <summary>
        /// Moves the selected entry is listbox up
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void uxMoveUp_Click(object sender, EventArgs e)
        {
            if (this.uxStartingPreferences.SelectedIndex > 0)
            {
                this.uxStartingPreferences.BeginUpdate();
                object old = this.uxStartingPreferences.SelectedItem;
                int currentIndex = this.uxStartingPreferences.SelectedIndex;
                this.uxStartingPreferences.Items.RemoveAt(currentIndex);
                this.uxStartingPreferences.Items.Insert(currentIndex - 1, old);
                this.uxStartingPreferences.SelectedIndex = currentIndex - 1;
                this.uxStartingPreferences.EndUpdate();
            }
        }

        /// <summary>
        /// Moves the selected enty in listbox down
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void uxMoveDown_Click(object sender, EventArgs e)
        {
            if (this.uxStartingPreferences.SelectedIndex >= 0 &&
                this.uxStartingPreferences.SelectedIndex < this.uxStartingPreferences.Items.Count - 1)
            {
                this.uxStartingPreferences.BeginUpdate();
                object old = this.uxStartingPreferences.SelectedItem;
                int currentIndex = this.uxStartingPreferences.SelectedIndex;
                this.uxStartingPreferences.Items.RemoveAt(currentIndex);
                this.uxStartingPreferences.Items.Insert(currentIndex + 1, old);
                this.uxStartingPreferences.SelectedIndex = currentIndex + 1;
                this.uxStartingPreferences.EndUpdate();
            }
        }

        private void uxBrowseCustomDirectory_Click(object sender, EventArgs e)
        {
            if (!String.IsNullOrEmpty(this.uxCustomDirectory.Text) &&
                System.IO.Directory.Exists(this.uxCustomDirectory.Text))
            {
                this.uxFolderDialog.SelectedPath = this.uxCustomDirectory.Text;
            }
            if (this.uxFolderDialog.ShowDialog() == DialogResult.OK)
            {
                this.uxCustomDirectory.Text = this.uxFolderDialog.SelectedPath;
            }
        }

        #endregion

        /// <summary>
        /// Loads shell entries from settings file and tries to get names from instances.
        /// If a entry couldn't be loaded it will be removed from the list and user will be notified.
        /// </summary>
        private void LoadShellEntries()
        {
            this.uxShellList.BeginUpdate();
            this.uxShellList.DataSource = null;
            this.uxShellList.Items.Clear();
            List<ShellHostEntry> hostList = new List<ShellHostEntry>();
            ShellLoader.AddShellEntry("Microsoft.VSPowerToys.VSCmdShell.Hosts.PowerShellHost",
                "Microsoft.VSPowerToys.VSCmdShell.PowerShellHost.dll");
            for (int i = 0; i < Properties.Settings.Default.ShellHostsLibraryLocation.Count; i++)
            {
                string location = Properties.Settings.Default.ShellHostsLibraryLocation[i];
                string typeName = Properties.Settings.Default.ShellHostsTypeNames[i];
                try
                {
                    IShellHost shell = ShellLoader.LoadShell(typeName, location);
                    hostList.Add(new ShellHostEntry(shell.ShellName, typeName));
                }
                catch (ShellLoadException ex)
                {
                    if (!Properties.Settings.Default.IsFirstTimeLaunch &&
                           !typeName.Equals("Microsoft.VSPowerToys.VSCmdShell.Hosts.PowerShellHost"))
                    {
                        MessageBox.Show(
                            String.Format(
                                System.Globalization.CultureInfo.CurrentCulture,
                                Strings.ShellLoadError_UserMessage,
                                typeName, ex.Message),
                            Strings.ShellLoadError_UserMessageCaption,
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Error,
                            MessageBoxDefaultButton.Button1,
                            MessageBoxOptions.DefaultDesktopOnly);
                    }
                    Properties.Settings.Default.ShellHostsLibraryLocation.RemoveAt(i);
                    Properties.Settings.Default.ShellHostsTypeNames.RemoveAt(i);
                    Properties.Settings.Default.Save();

                }
            }
            hostList.Add(new ShellHostEntry("<Browse for more...>", "<AddNew>"));
            this.uxShellList.DisplayMember = "Name";
            this.uxShellList.ValueMember = "TypeName";
            this.uxShellList.DataSource = hostList;
            try
            {
                this.uxShellList.SelectedValue = Properties.Settings.Default.SelectedShellTypeName;
            }
            catch (InvalidOperationException)
            {
                this.uxShellList.SelectedIndex = 0;
            }
            this.uxShellList.EndUpdate();
        }

        /// <summary>
        /// Fills the starting directory preferences items
        /// </summary>
        private void FillStartingDirectoryPreferences()
        {
            StartingDirectorySetting prefs = StartingDirectorySetting.UserSettings;
            this.uxStartingPreferences.BeginUpdate();
            this.uxStartingPreferences.Items.Clear();
            foreach (StartingDirectoryPreference item in prefs.Preferences)
            {
                this.uxStartingPreferences.Items.Add(Utils.GetEnumDescription(item));
            }
            this.uxStartingPreferences.EndUpdate();
            if (prefs.CustomDirectory != null)
            {
                this.uxCustomDirectory.Text = prefs.CustomDirectory;
            }

        }

        /// <summary>
        /// This method is called when selection is changed on the shell list. If new selection is "Add New",
        /// user is presented witha open file dialog to choose an assembly file containing IShellHost implementations.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void uxShellList_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (this.uxShellList.SelectedValue == null)
            {
                return;
            }
            if (this.uxShellList.SelectedValue.Equals("<AddNew>"))
            {
                DialogResult dr = uxShellFileDialog.ShowDialog();
                if (dr == DialogResult.OK)
                {
                    try
                    {
                        ShellLoader.AddShellHosts(uxShellFileDialog.FileName);
                        this.LoadShellEntries();
                    }
                    catch (ShellLoadException ex)
                    {
                        MessageBox.Show(
                        String.Format(
                            System.Globalization.CultureInfo.CurrentCulture,
                            Strings.ShellLoadError_AssemblyError,
                            uxShellFileDialog.FileName, ex.Message),
                        Strings.ShellLoadError_UserMessageCaption,
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error,
                        MessageBoxDefaultButton.Button1,
                        MessageBoxOptions.DefaultDesktopOnly);
                        this.uxShellList.SelectedIndex = 0;
                    }
                }
                else
                {
                    this.uxShellList.SelectedIndex = 0;
                }
            }
        }

        private void ShowFontBackColorChange(object sender, EventArgs e)
        {
            DialogResult dr = this.uxBackColorDialog.ShowDialog();
            if (dr == DialogResult.OK)
            {
                this.uxSample.BackColor = this.uxBackColorDialog.Color;
            }
        }


        #region IDTToolsOptionsPage Members

        /// <seealso cref="EnvDTE.IDTToolsOptionsPage.GetProperties"/>
        public void GetProperties(ref object PropertiesObject)
        {

        }

        /// <seealso cref="EnvDTE.IDTToolsOptionsPage.OnAfterCreated"/>
        public void OnAfterCreated(EnvDTE.DTE DTEObject)
        {
        }

        /// <seealso cref="EnvDTE.IDTToolsOptionsPage.OnCancel"/>
        public void OnCancel()
        {

        }

        /// <seealso cref="EnvDTE.IDTToolsOptionsPage.OnHelp"/>
        public void OnHelp()
        {
        }

        /// <summary>
        /// Saves the settings and closes the form. If shell entry is changed, user is also warned that
        /// selection only takes affect after restart.
        /// </summary>
        /// <seealso cref="EnvDTE.IDTToolsOptionsPage.OnOK"/>
        public void OnOK()
        {
            Properties.Settings.Default.ConsoleFontUseVS = this.uxFontUseVS.Checked;
            Properties.Settings.Default.SelectedShellTypeName = this.uxShellList.SelectedValue.ToString();
            if (!Properties.Settings.Default.SelectedShellTypeName.Equals(this.previouslySelectedShell))
            {
                MessageBox.Show(
                            Strings.Options_ShellChangeText,
                            Strings.Options_ShellChangeCaption,
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Warning,
                            MessageBoxDefaultButton.Button1,
                            MessageBoxOptions.DefaultDesktopOnly);
            }
            Properties.Settings.Default.IsFirstTimeLaunch = false;
            if (!this.uxFontUseVS.Checked)
            {
                Properties.Settings.Default.ConsoleFont = this.uxFontDialog.Font;
                Properties.Settings.Default.ConsoleFontColor = this.uxColorDialog.Color;
                Properties.Settings.Default.ConsoleFontBackColor = this.uxBackColorDialog.Color;
            }

            // Save starting directory preferences
            Collection<StartingDirectoryPreference> list = StartingDirectorySetting.UserSettings.Preferences;
            list.Clear();
            foreach (object description in this.uxStartingPreferences.Items)
            {
                list.Add((StartingDirectoryPreference)(Utils.GetEnumFromDescription(
                    typeof(StartingDirectoryPreference), description.ToString())));
            }
            StartingDirectorySetting.UserSettings.CustomDirectory = this.uxCustomDirectory.Text;
            Properties.Settings.Default.Save();
        }

        #endregion

    }

    /// <summary>
    /// Options for starting directory locations
    /// </summary>
    public enum StartingDirectoryPreference
    {
        [Description("Solution Root Directory")] SolutionDirectory, 
        [Description("Project Root Directory")] ProjectDirectory, 
        [Description("Custom Directory")] Custom, 
        [Description("Shell Default")] ShellDefault
    };

    /// <summary>
    /// A custom object to hold user settings for starting directory
    /// </summary>
    [Serializable]
    public class StartingDirectorySetting
    {

        #region Private Members

        private string customDirectory;

        private Collection<StartingDirectoryPreference> preferences;

        #endregion

        #region Public Properties

        /// <summary>
        /// User's custom directory. This entry can be null or empty
        /// </summary>
        public string CustomDirectory
        {
            get
            {
                return customDirectory;
            }
            set
            {
                this.customDirectory = value;
            }
        }

        /// <summary>
        /// Users preferences in order
        /// </summary>
        public Collection<StartingDirectoryPreference> Preferences
        {
            get
            {
                if (preferences == null)
                {
                    Collection<StartingDirectoryPreference> prefs = new Collection<StartingDirectoryPreference>();
                    this.preferences = prefs;
                }
                return preferences;
            }
        }

        #endregion

       
        #region Static Methods

        /// <summary>
        /// Gets user settings from Properties class, if settings are not available
        /// this method creates a new settings object and saves it.
        /// 
        /// Default values for preference ordering has to be provided at this method, because
        /// when settings are deserialized all choices in the setting is appended to current list
        /// on the setting object thus the list has to be empty when the object is constructed.
        /// </summary>
        public static StartingDirectorySetting UserSettings
        {
            get
            {
                StartingDirectorySetting prefs = Properties.Settings.Default.StartingDirectoryPreference;
                if (prefs == null)
                {
                    prefs = new StartingDirectorySetting();
                    prefs.Preferences.Add(StartingDirectoryPreference.ProjectDirectory);
                    prefs.Preferences.Add(StartingDirectoryPreference.SolutionDirectory);
                    prefs.Preferences.Add(StartingDirectoryPreference.Custom);
                    prefs.Preferences.Add(StartingDirectoryPreference.ShellDefault);
                    Properties.Settings.Default.StartingDirectoryPreference = prefs;
                    Properties.Settings.Default.Save();
                }
                return prefs;
            }
        }

        #endregion
    }

    /// <summary>
    /// A simple pair class to be used as entries in shell selection dropdown list
    /// </summary>
    class ShellHostEntry
    {
        private string name;
        private string typeName;


        /// <summary>
        /// Name of the shell host module
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        public string Name
        {
            get
            {
                return name;
            }
        }

        /// <summary>
        /// Full type name of the shell host module
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        public string TypeName
        {
            get
            {
                return typeName;
            }
        }
        public ShellHostEntry(string name, string type)
        {
            this.name = name;
            this.typeName = type;
        }
    }
}